package joc.transformer;

import javassist.CtClass;
import javassist.CtMethod;
import joc.JOCException;
import joc.PostAfter;
import joc.PostBefore;
import joc.PostCondition;

public class PostConditionMethodTransformer extends DeclaredMethodTransformer {

	public PostConditionMethodTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz, CtMethod method) throws Exception {
		if (method.hasAnnotation(PostCondition.class)) {
			PostCondition annotation = (PostCondition) method.getAnnotation(PostCondition.class);
			int i = 0;
			for (Class<?> pcClass : annotation.value()) {
				applyPostCondition(method, pcClass.getName(), i);
				i++;
			}
		}
	}
	
	private void applyPostCondition(CtMethod method, String pcClassName, int pcNum) throws Exception {
		logger.info("...has post-condition " + pcClassName);
		CtClass annotationClass = transformer.getPool().get(pcClassName);
		String pcNameQuoted = "\"" + pcClassName + "\"";
		String insertBefore = "if (!joc.Cache#containsPostCondition(this, " + pcNameQuoted + ")) { ";
		insertBefore += "joc.Cache#savePostCondition(this, " + pcNameQuoted + ", new " + pcClassName + "()); ";
		insertBefore += "} ";
		String insertAfter = "";
		for (CtMethod pcMethod : annotationClass.getDeclaredMethods()) {
			if (pcMethod.hasAnnotation(PostBefore.class) || pcMethod.hasAnnotation(PostAfter.class)) {
				String params = "";
				if ((pcMethod.getParameterTypes().length - 1) > method.getParameterTypes().length) {
					throw new JOCException("method " + pcMethod.getLongName() + " can have no more than " + (method.getParameterTypes().length + 1) + " arguments: the contract class/interface and up to " + method.getParameterTypes().length + " arguments from method " + method.getLongName());
				}
				for (int i = 1; i <= pcMethod.getParameterTypes().length - 1; i++) {
					params += ", $" + i;
				}
				if (pcMethod.hasAnnotation(PostBefore.class)) {
					insertBefore += "((" + pcClassName + ") joc.Cache#getPostCondition(this, " + pcNameQuoted + "))." + pcMethod.getName() + "(this" + params + "); ";
				}
				else {
					insertAfter += "joc.Behavior#postCondition(((" + pcClassName + ") joc.Cache#getPostCondition(this, " + pcNameQuoted + "))." + pcMethod.getName() + "(this" + params + ")); ";
				}
			}
		}
		logger.info("insertBefore: " + insertBefore);
		logger.info("insertAfter: " + insertAfter);
		method.insertBefore("{ " + insertBefore + " }");
		method.insertAfter("{ " + insertAfter + " }");
	}

}
